package org.jasper.impl.base;

import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import org.jasper.api.base.KieBaseModel;
import org.jasper.api.module.KieModuleModel;
import org.jasper.api.session.KieSessionModel;
import org.jasper.api.template.RuleTemplateModel;
import org.jasper.constants.DeclarativeAgendaOption;
import org.jasper.constants.EqualityBehaviorOption;
import org.jasper.constants.EventProcessingOption;
import org.jasper.impl.module.AbstractXStreamConverter;
import org.jasper.impl.session.KieSessionModelImpl;
import org.jasper.impl.template.RuleTemplateModelImpl;
import org.jasper.impl.utils.StringUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Created by OP038074 on 2018/7/18.
 */
public class KieBaseModelImpl implements KieBaseModel {

    private String name;
    private KieModuleModel kModule;
    private boolean isDefault = false;
    private EventProcessingOption eventProcessingMode = EventProcessingOption.CLOUD;
    private EqualityBehaviorOption equalsBehavior = EqualityBehaviorOption.IDENTITY;
    private DeclarativeAgendaOption declarativeAgenda = DeclarativeAgendaOption.DISABLED;
    private String scope = "javax.enterprise.context.ApplicationScoped";
    private List<String> packages;
    private Set<String> includes= new HashSet<String>();
    private List<RuleTemplateModel>      ruleTemplates = new ArrayList<RuleTemplateModel>();
    private Map<String, KieSessionModel> kSessions = new HashMap<String, KieSessionModel>();

    public KieBaseModelImpl() {
    }
    public KieBaseModelImpl(KieModuleModel kModule,
                            String name) {
        this.kModule = kModule;
        this.includes = new HashSet<String>();
        this.name = name;
        this.kSessions = Collections.emptyMap();
    }

    public String getName() {
        return name;
    }

    public void setKModule(KieModuleModel kieProject) {
        this.kModule = kieProject;
    }

    public boolean isDefault() {
        return isDefault;
    }

    public EventProcessingOption getEventProcessingMode() {
        return eventProcessingMode;
    }

    public EqualityBehaviorOption getEqualsBehavior() {
        return equalsBehavior;
    }

    public DeclarativeAgendaOption getDeclarativeAgenda() {
        return declarativeAgenda;
    }

    public List<String> getPackages() {
        return packages != null ? packages : Collections.<String>emptyList();
    }

    public Set<String> getIncludes() {
        return Collections.unmodifiableSet( includes );
    }

    public List<RuleTemplateModel> getRuleTemplates() {
        return Collections.unmodifiableList( ruleTemplates );
    }

    public Map<String, KieSessionModel> getKieSessionModels() {
        return Collections.unmodifiableMap( kSessions );
    }

    public KieBaseModel setDefault(boolean isDefault) {
        this.isDefault = isDefault;
        return this;
    }

    public KieBaseModel setEventProcessingMode(EventProcessingOption eventProcessingMode) {
        this.eventProcessingMode = eventProcessingMode;
        return this;
    }

    public KieBaseModel setEqualsBehavior(EqualityBehaviorOption equalsBehaviour) {
        this.equalsBehavior = equalsBehaviour;
        return this;
    }

    public KieBaseModel setDeclarativeAgenda(DeclarativeAgendaOption declarativeAgenda) {
        this.declarativeAgenda = declarativeAgenda;
        return this;
    }

    public KieBaseModel setScope(String scope) {
        this.scope = scope;
        return this;
    }

    @Override
    public String getScope() {
        return this.scope;
    }

    public KieBaseModel addPackage(String pkg) {
        if ( packages == null ) {
            packages = new ArrayList<String>();
        }
        packages.add( pkg );
        return this;
    }

    public KieBaseModel addInclude(String kBaseQName) {
        this.includes.add( kBaseQName );
        return this;
    }

    public Map<String, KieSessionModel> getRawKieSessionModels() {
        return kSessions;
    }

    public List<RuleTemplateModel> getRawRuleTemplates() {
        return ruleTemplates;
    }

    private void setKSessions(Map<String, KieSessionModel> kSessions) {
        this.kSessions = kSessions;
    }

    public KieSessionModel newKieSessionModel(String name) {
        KieSessionModel kieSessionModel = new KieSessionModelImpl( this, name );
        Map<String, KieSessionModel> newMap = new HashMap<String, KieSessionModel>();
        newMap.putAll( this.kSessions );
        newMap.put( kieSessionModel.getName(), kieSessionModel );
        setKSessions( newMap );

        return kieSessionModel;
    }

    public static class KBaseConverter extends AbstractXStreamConverter {
        public KBaseConverter() {
            super( KieBaseModelImpl.class );
        }
        public void marshal(Object value,
                            HierarchicalStreamWriter writer,
                            MarshallingContext context) {
            KieBaseModelImpl kBase = (KieBaseModelImpl) value;
            writer.addAttribute( "name", kBase.getName() );
            writer.addAttribute( "default", Boolean.toString(kBase.isDefault()) );
            if ( kBase.getEventProcessingMode() != null ) {
                writer.addAttribute( "eventProcessingMode", kBase.getEventProcessingMode().getMode() );
            }
            if ( kBase.getEqualsBehavior() != null ) {
                writer.addAttribute( "equalsBehavior", kBase.getEqualsBehavior().toString().toLowerCase() );
            }
            if ( kBase.getDeclarativeAgenda() != null ) {
                writer.addAttribute( "declarativeAgenda", kBase.getDeclarativeAgenda().toString().toLowerCase() );
            }

            if ( kBase.getScope() != null ) {
                writer.addAttribute( "scope", kBase.getScope() );
            }

            if ( ! kBase.getPackages().isEmpty() ) {
                StringBuilder buf = new StringBuilder();
                boolean first = true;
                for( String pkg : kBase.getPackages() ) {
                    if( first ) {
                        first = false;
                    } else {
                        buf.append( ", " );
                    }
                    buf.append( pkg );
                }
                writer.addAttribute( "packages", buf.toString() );
            }
            if ( !kBase.getIncludes().isEmpty() ) {
                StringBuilder sb = new StringBuilder();
                boolean insertComma = false;
                for ( String include : kBase.getIncludes() ) {
                    if ( insertComma ) {
                        sb.append( ", " );
                    }
                    sb.append( include );
                    if ( !insertComma ) {
                        insertComma = true;
                    }
                }
                writer.addAttribute( "includes", sb.toString() );
            }

            for ( RuleTemplateModel ruleTemplateModel : kBase.getRuleTemplates()) {
                writeObject( writer, context, "ruleTemplate", ruleTemplateModel);
            }

            for ( KieSessionModel kSessionModel : kBase.getKieSessionModels().values()) {
                writeObject( writer, context, "ksession", kSessionModel);
            }
        }

        public Object unmarshal(HierarchicalStreamReader reader,
                                final UnmarshallingContext context) {
            final KieBaseModelImpl kBase = new KieBaseModelImpl();

            String kbaseName = reader.getAttribute( "name" );
            kBase.name = kbaseName != null ? kbaseName : StringUtils.uuid();

            kBase.setDefault( "true".equals(reader.getAttribute( "default" )) );

            String eventMode = reader.getAttribute( "eventProcessingMode" );
            if ( eventMode != null ) {
                kBase.setEventProcessingMode( EventProcessingOption.determineEventProcessingMode( eventMode ) );
            }

            String equalsBehavior = reader.getAttribute( "equalsBehavior" );
            if ( equalsBehavior != null ) {
                kBase.setEqualsBehavior( EqualityBehaviorOption.determineEqualityBehavior( equalsBehavior ) );
            }

            String declarativeAgenda = reader.getAttribute( "declarativeAgenda" );
            if ( declarativeAgenda != null ) {
                kBase.setDeclarativeAgenda( DeclarativeAgendaOption.determineDeclarativeAgenda( declarativeAgenda ) );
            }

            String scope = reader.getAttribute( "scope" );
            if ( scope != null ) {
                kBase.setScope( scope.trim() );
            }

            String pkgs = reader.getAttribute( "packages" );
            if( pkgs != null ) {
                for( String pkg : pkgs.split( "," ) ) {
                    kBase.addPackage( pkg.trim() );
                }
            }

            String includes = reader.getAttribute( "includes" );
            if( includes != null ) {
                for( String include : includes.split( "," ) ) {
                    kBase.addInclude( include.trim() );
                }
            }

            readNodes( reader, new AbstractXStreamConverter.NodeReader() {
                public void onNode(HierarchicalStreamReader reader,
                                   String name,
                                   String value) {
                    if ( "ksession".equals( name ) ) {
                        KieSessionModelImpl kSession = readObject( reader, context, KieSessionModelImpl.class );
                        kBase.getRawKieSessionModels().put( kSession.getName(), kSession );
                        kSession.setKBase(kBase);
                    } else if ( "ruleTemplate".equals( name ) ) {
                        RuleTemplateModelImpl ruleTemplate = readObject( reader, context, RuleTemplateModelImpl.class );
                        kBase.getRawRuleTemplates().add( ruleTemplate );
                        ruleTemplate.setKBase( kBase );
                    }

                    // @TODO we don't use support nested includes
//                    if ( "includes".equals( name ) ) {
//                        for ( String include : readList( reader ) ) {
//                            kBase.addInclude( include );
//                        }
//                    }
                }
            } );
            return kBase;
        }
    }
}
